﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using Nova.Parsing;
using Nova.Rendering;

namespace Nova.CodeDOM
{
    /// <summary>
    /// Redirects execution to the specified <see cref="Label"/> or <see cref="SwitchItem"/> (<see cref="Case"/> or <see cref="Default"/>).
    /// </summary>
    public class Goto : Statement
    {
        #region /* FIELDS */

        // Should evaluate to a GotoTargetRef (LabelRef or a SwitchItemRef) or an UnresolvedRef
        protected SymbolicRef _target;

        // The constant expression used by a "goto case ..."
        protected Expression _constantExpression;

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a <see cref="Goto"/> to a <see cref="Label"/>.
        /// </summary>
        public Goto(Label label)
        {
            Target = new LabelRef(label);
        }

        /// <summary>
        /// Create a <see cref="Goto"/> to a <see cref="SwitchItem"/>.
        /// </summary>
        public Goto(SwitchItem item)
        {
            Target = new SwitchItemRef(item);
        }

        /// <summary>
        /// Create a <see cref="Goto"/> to a string name.
        /// </summary>
        public Goto(string name)
        {
            Target = new UnresolvedRef(name);
        }

        /// <summary>
        /// Create a <see cref="Goto"/> to a constant <see cref="Expression"/>.
        /// </summary>
        public Goto(Expression constantExpression)
        {
            ConstantExpression = constantExpression;
            Target = new UnresolvedRef(Case.ParseToken + " " + _constantExpression.AsString());
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The target <see cref="GotoTargetRef"/> (<see cref="LabelRef"/> or <see cref="SwitchItemRef"/>) or <see cref="UnresolvedRef"/>.
        /// </summary>
        public SymbolicRef Target
        {
            get { return _target; }
            set { SetField(ref _target, value, true); }
        }

        /// <summary>
        /// The constant expression if this is a "goto case ...", otherwise null.
        /// </summary>
        public Expression ConstantExpression
        {
            get { return _constantExpression; }
            set { SetField(ref _constantExpression, value, true); }
        }

        /// <summary>
        /// The hidden GotoTargetRef (or UnresolvedRef) that represents the goto target if we have a "goto case ...".
        /// </summary>
        public override SymbolicRef HiddenRef
        {
            get { return (_constantExpression != null ? _target : null); }
        }

        /// <summary>
        /// True if this is a 'goto case ...'.
        /// </summary>
        public bool IsGotoCase
        {
            get { return (_constantExpression != null); }
        }

        /// <summary>
        /// True if this is a 'goto default'.
        /// </summary>
        public bool IsGotoDefault
        {
            get { return (_target.AsString() == Default.ParseToken); }
        }

        /// <summary>
        /// The keyword associated with the <see cref="Statement"/>.
        /// </summary>
        public override string Keyword
        {
            get { return ParseToken; }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Deep-clone the code object.
        /// </summary>
        public override CodeObject Clone()
        {
            Goto clone = (Goto)base.Clone();
            clone.CloneField(ref clone._target, _target);
            clone.CloneField(ref clone._constantExpression, _constantExpression);
            return clone;
        }

        #endregion

        #region /* PARSING */

        /// <summary>
        /// The token used to parse the code object.
        /// </summary>
        public const string ParseToken = "goto";

        internal static void AddParsePoints()
        {
            Parser.AddParsePoint(ParseToken, Parse, typeof(IBlock));
        }

        /// <summary>
        /// Parse a <see cref="Goto"/>.
        /// </summary>
        public static Goto Parse(Parser parser, CodeObject parent, ParseFlags flags)
        {
            return new Goto(parser, parent);
        }

        protected Goto(Parser parser, CodeObject parent)
            : base(parser, parent)
        {
            parser.NextToken();  // Move past 'goto'
            Token startToken = parser.Token;
            string target = "";

            // Handle "goto case ..."
            if (parser.TokenText == Case.ParseToken)
            {
                parser.NextToken();  // Move past 'case'
                SetField(ref _constantExpression, Expression.Parse(parser, this, true, ParseTokenTerminator), false);
                target = Case.ParseToken + " " + _constantExpression.AsString();
            }
            else  // Handle "goto <label>" or "goto default"
            {
                // Build a symbolic reference from all text up to the ';' (or EOL)
                bool first = true;
                while (parser.TokenText != Terminator && parser.Token != null && !parser.Token.IsFirstOnLine)
                {
                    target += (first ? "" : parser.Token.LeadingWhitespace) + parser.TokenText;
                    parser.NextToken();
                    first = false;
                }
            }
            SetField(ref _target, new UnresolvedRef(target, startToken.LineNumber, startToken.ColumnNumber), false);

            ParseTerminator(parser);
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// True if the <see cref="Statement"/> has parens around its argument.
        /// </summary>
        public override bool HasArgumentParens
        {
            get { return false; }
        }

        /// <summary>
        /// True if the <see cref="Statement"/> has a terminator character by default.
        /// </summary>
        public override bool HasTerminatorDefault
        {
            get { return true; }
        }

        /// <summary>
        /// Determines if the code object only requires a single line for display.
        /// </summary>
        public override bool IsSingleLine
        {
            get { return (base.IsSingleLine && (_target == null || (!_target.IsFirstOnLine && _target.IsSingleLine))); }
            set
            {
                base.IsSingleLine = value;
                if (value && _target != null)
                {
                    _target.IsFirstOnLine = false;
                    _target.IsSingleLine = true;
                }
            }
        }

        #endregion

        #region /* RENDERING */

        protected override void AsTextArgument(CodeWriter writer, RenderFlags flags)
        {
            // If we have a constant expression ("goto case ..."), always render it instead of
            // the target reference.
            if (_constantExpression != null)
            {
                writer.Write(Case.ParseToken + " ");
                _constantExpression.AsText(writer, flags);
            }
            else
                _target.AsText(writer, flags);
        }

        #endregion
    }
}
